package com.zxjbyte.yiyi.framework.common.core.okhttp;

import cn.hutool.core.lang.Singleton;
import com.google.common.collect.Lists;
import com.zxjbyte.yiyi.framework.common.core.okhttp.callback.IProgressCallback;
import com.zxjbyte.yiyi.framework.common.core.okhttp.retry.RetryInterceptor;
import com.zxjbyte.yiyi.framework.common.core.okhttp.retry.RetryPolicy;
import com.zxjbyte.yiyi.framework.common.core.ssl.DisableVerifyingSslTrustManager;
import com.zxjbyte.yiyi.framework.common.core.ssl.TrustAllHostnameVerifier;
import com.zxjbyte.yiyi.framework.common.util.CollXUtil;
import lombok.SneakyThrows;
import okhttp3.*;
import okhttp3.logging.HttpLoggingInterceptor;

import javax.net.ssl.*;
import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static cn.hutool.core.util.ObjectUtil.isNotNull;

/**
 * doc:  https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/
 * @Author zhangxingjia
 * @Date 2023/4/20 10:29
 * @Version: 1.0
 */
public class OkHttpCliHolder {

    private static final OkHttpClient initHttpClient;
    private static final Duration DEFAULT_CONNECTION_TIMEOUT = Duration.ofMillis(10000L);
    private static final Duration DEFAULT_READ_TIMEOUT = Duration.ofMillis(15000L);
    private static final Duration DEFAULT_WRITE_TIMEOUT = Duration.ofMillis(15000L);
    private final OkHttpClient.Builder builder;
    private Duration connectTimeout;
    private Duration readTimeout;
    private Duration writeTimeout;
    private Duration callTimeout;
    private Proxy proxy;
    private ProxySelector proxySelector;
    private Authenticator proxyAuthenticator;
    private CookieJar cookieJar;
    private RetryPolicy retryPolicy;
    private Boolean disableVerifyingSsl;
    private LoggerLevel level;
    private final List<Interceptor> interceptors = Lists.newArrayList();
    private SSLSocketFactory sslSocketFactory;
    private X509TrustManager trustManager;

    static {
        OkHttpClient.Builder initBuilder = new OkHttpClient.Builder();
        // 添加网络拦截器，异步请求时实现下载进度的监听
        initBuilder.addNetworkInterceptor(chain -> {
            final Request request = chain.request();
            final Response originalResponse = chain.proceed(request);
            if (request.tag() instanceof IProgressCallback) {
                final IProgressCallback callback = (IProgressCallback) request.tag();
                return originalResponse.newBuilder()
                        .body(new CountingResponseBody(originalResponse.body(), callback))
                        .build();
            }
            return originalResponse;
        });
        initHttpClient = initBuilder.build();
    }

    public OkHttpCliHolder(){
        this.builder = initHttpClient.newBuilder();
    }

    public static OkHttpCliHolder create(){
        return new OkHttpCliHolder();
    }

    public static OkHttpCliHolder defaultCreate(){
        return create()
                .connectTimeout(DEFAULT_CONNECTION_TIMEOUT)
                .readTimeout(DEFAULT_READ_TIMEOUT)
                .writeTimeout(DEFAULT_WRITE_TIMEOUT);
    }

    public OkHttpClient build(){
        // 连接超时时间，即建立连接的最长等待时间
        if(isNotNull(this.connectTimeout)){
            this.builder.connectTimeout(this.connectTimeout.toMillis(), TimeUnit.MILLISECONDS);
        }
        // 读取超时时间，即从服务器读取数据的最长等待时间
        if(isNotNull(this.readTimeout)){
            this.builder.writeTimeout(this.readTimeout.toMillis(), TimeUnit.MILLISECONDS);
        }
        // 写入超时时间，即向服务器写入数据的最长等待时间
        if(isNotNull(this.writeTimeout)){
            this.builder.readTimeout(this.writeTimeout.toMillis(), TimeUnit.MILLISECONDS);
        }
        // 完整的调用超时时间，即连接、读取和写入的最长等待时间。如果在这个时间内没有得到响应，将抛出SocketTimeoutException异常
        if(isNotNull(this.callTimeout)){
            this.builder.callTimeout(this.callTimeout.toMillis(), TimeUnit.MILLISECONDS);
        }
        // 代理服务器
        if(isNotNull(this.proxy)){
            this.builder.proxy(this.proxy);
        }
        // 代理服务器的策略
        if(isNotNull(this.proxySelector)){
            this.builder.proxySelector(this.proxySelector);
        }
        // 代理服务器的身份验证
        if(isNotNull(this.proxyAuthenticator)){
            this.builder.proxyAuthenticator(this.proxyAuthenticator);
        }
        // 添加重试策略
        if (retryPolicy != null) {
            this.builder.addInterceptor(new RetryInterceptor(retryPolicy));
        }
        // 添加拦截器
        if(CollXUtil.isNotEmpty(this.interceptors)){
            this.builder.interceptors().addAll(this.interceptors);
        }
        // 用于处理HTTP cookie
        if(isNotNull(this.cookieJar)){
            this.builder.cookieJar(this.cookieJar);
        }
        // 关闭ssl校验
        if (Boolean.TRUE.equals(this.disableVerifyingSsl)) {
            disableCertCheck();
        }
        // 添加日志拦截器
        if(isNotNull(this.level) && LoggerLevel.NONE != this.level){
            addLoggingInterceptor(this.level);
        }
        // 添加ssl证书
        if(isNotNull(this.sslSocketFactory) && isNotNull(this.trustManager)){
            this.builder.sslSocketFactory(sslSocketFactory, trustManager);
        }
        this.builder.protocols(Collections.singletonList(Protocol.HTTP_1_1));
        return this.builder.build();
    }

    public OkHttpCliHolder connectTimeout(Duration timeout) {
        this.connectTimeout = timeout;
        return this;
    }

    public OkHttpCliHolder readTimeout(Duration readTimeout) {
        this.readTimeout = readTimeout;
        return this;
    }

    public OkHttpCliHolder writeTimeout(Duration writeTimeout) {
        this.writeTimeout = writeTimeout;
        return this;
    }

    public OkHttpCliHolder callTimeout(Duration callTimeout) {
        this.callTimeout = callTimeout;
        return this;
    }

    public OkHttpCliHolder proxy(String proxyHost, int proxyPort) {
        this.proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
        return this;
    }

    public OkHttpCliHolder proxySelector(ProxySelector proxySelector) {
        this.proxySelector = proxySelector;
        return this;
    }

    public OkHttpCliHolder proxyAuthenticator(Authenticator proxyAuthenticator) {
        this.proxyAuthenticator = proxyAuthenticator;
        return this;
    }

    public OkHttpCliHolder retry(){
        this.retryPolicy = Singleton.get(RetryPolicy.class);
        return this;
    }

    public OkHttpCliHolder retry(RetryPolicy retryPolicy){
        this.retryPolicy = retryPolicy;
        return this;
    }

    public OkHttpCliHolder retry(long sleepTime, int attemptNumber){
        this.retryPolicy = Singleton.get(RetryPolicy.class, sleepTime, attemptNumber);
        return this;
    }

    public OkHttpCliHolder interceptors(List<Interceptor> interceptors) {
        if(CollXUtil.isNotEmpty(interceptors)){
            this.interceptors.addAll(interceptors);
        }
        return this;
    }

    public OkHttpCliHolder interceptor(Interceptor interceptor) {
        this.interceptors.add(interceptor);
        return this;
    }

    public OkHttpCliHolder cookieJar(CookieJar cookieJar) {
        this.cookieJar = cookieJar;
        return this;
    }

    public OkHttpCliHolder skipSsl(){
        this.disableVerifyingSsl = Boolean.TRUE;
        return this;
    }

    public OkHttpCliHolder log(){
        return log(LoggerLevel.BODY);
    }

    public OkHttpCliHolder log(LoggerLevel level){
        this.level = level;
        return this;
    }

    public OkHttpCliHolder enableExternalCert(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager) {
        this.sslSocketFactory = sslSocketFactory;
        this.trustManager = trustManager;
        return this;
    }

    public OkHttpCliHolder enableExternalCert(String filename) {
        enableExternalCertificates(filename);
        return this;
    }


    /**
     * 开启外部证书校验
     * 代码参考来自：
     * https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java
     */
    @SneakyThrows
    private void enableExternalCertificates(String filename){
        Collection<? extends Certificate> certificates = null;
        try (FileInputStream fis = new FileInputStream(filename)) {
            certificates = CertificateFactory.getInstance("X.509").generateCertificates(fis);
        }

        if (certificates == null || certificates.isEmpty()) {
            throw new IllegalArgumentException("expected non-empty set of trusted certificates");
        }

        char[] password = "password".toCharArray(); // Any password will work.

        // Put the certificates a key store.
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        // By convention, 'null' creates an empty key store.
        keyStore.load(null, password);

        int index = 0;
        for (Certificate certificate : certificates) {
            String certificateAlias = Integer.toString(index++);
            keyStore.setCertificateEntry(certificateAlias, certificate);
        }

        // Use it to build an X509 trust manager.
        KeyManagerFactory keyManagerFactory =
                KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keyStore, password);
        TrustManagerFactory trustManagerFactory =
                TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);

        final KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
        final TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(keyManagers, trustManagers, null);

        this.sslSocketFactory = sslContext.getSocketFactory();
        this.trustManager = (X509TrustManager) trustManagers[0];
    }

    /**
     * 关闭证书检查
     */
    @SneakyThrows
    private void disableCertCheck(){
        X509TrustManager x509TrustManager = DisableVerifyingSslTrustManager.INSTANCE;
        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(new KeyManager[0], new TrustManager[]{x509TrustManager}, Singleton.get(SecureRandom.class));
        this.builder.sslSocketFactory(sslContext.getSocketFactory(), x509TrustManager);
        this.builder.hostnameVerifier(TrustAllHostnameVerifier.INSTANCE);
    }

    /**
     * Add log interceptor to okhttp3
     * @param level
     */
    private void addLoggingInterceptor(LoggerLevel level){
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(Slf4jLogger.INSTANCE);
        loggingInterceptor.setLevel(level.getLevel());
        this.builder.addInterceptor(loggingInterceptor);
    }

}
